#include "network.h"
#include "mem_alloc.h"

#include <errno.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

const char * remote_ip = 0;
int remote_port = 0;

const char * local_ip = "0.0.0.0";
int local_port = 0;

int is_listen = 0;

void Usage(const char * prog)
{
    printf("Usage : %s [ip] port          -- receive file\n", prog);
    printf("        %s ip port file ...   -- send file\n", prog);
    exit(0);
}

typedef struct FileInfo
{
    char name[256];
    int  name_translated;
    FILE  * file;
} FileInfo;

void on_read_file(network * net, int fd, void * ctx)
{
    FileInfo * fi = (FileInfo*)ctx;
    char buff[1024] = {0};
    int ret = recv(fd, buff, sizeof(buff), 0);
    if (ret < 0) {
        if (errno == EAGAIN || errno == EWOULDBLOCK) {
            return;
        }

        printf("recv file %s failed : %s\n", fi->name, strerror(errno));
        goto done;
    }

    if (ret == 0) {
        printf("recv file %s done\n", fi->name);
        goto done;
    }

    if (fwrite(buff, 1, ret, fi->file) != (size_t)ret) {
        printf("write to file %s faild: %s\n", fi->name, strerror(errno));
        goto done;
    }
    printf("write %d byte to file %s\n", ret, fi->name);
    return;
done:
    network_del(net, fd);
    if (fi->file) {
        fclose(fi->file);
    }
    close(fd);
    RELEASE_OBJECT(FileInfo, fi);
}

void on_read_name(network * net, int fd, void * ctx)
{
    FileInfo * fi = (FileInfo*)ctx;
    int ret = recv(fd, fi->name+fi->name_translated, 256-fi->name_translated, 0);
    if (ret < 0) {
        if (errno == EAGAIN || errno == EWOULDBLOCK) {
            return;
        }

        printf("recv failed : %s\n", strerror(errno));
        goto failed;
    }

    if (ret == 0) {
        printf("recv name failed: closed by peer\n");
        goto failed;
    }

    if (fi->name_translated < 256) {
        fi->file = fopen(fi->name, "wb");
        if (fi->file == 0) {
            printf("open file %s failed: %s\n", fi->name, strerror(errno));
            goto failed;
        }
        network_set(net, fd, on_read_file, 0, fi);
    } 
    return;
failed:
    network_del(net, fd);
    if (fi->file) {
        fclose(fi->file);
    }
    close(fd);
    RELEASE_OBJECT(FileInfo, fi);
}

void on_accept(network * net, int listen_fd, int new_fd,
               const char * ip, unsigned short port, void * ctx)
{
    printf("accept from %s:%d\n", ip, port);

    FileInfo * fi = GET_OBJECT(FileInfo);
    if (fi == 0) {
        printf("GET_OBJECT failed\n");
        close(new_fd);
        return;
    }

    memset(fi->name, 0, 256);
    fi->name_translated = 0;
    fi->file = 0;
    network_set(net, new_fd, on_read_name, 0, fi);
}

void on_write_file(network * net, int fd, void * ctx)
{
    FileInfo * fi = (FileInfo*)ctx;
    char buff[1024] = {0};
    int  len = 0;
    int send_len = 0;
    if (feof(fi->file) ) {
        printf("send file %s done\n", fi->name);
        goto done;
    }
    len = fread(buff, 1, 1024, fi->file);
    if (len > 0) {
        send_len = send(fd, buff, len, 0);
        if (send_len < 0) {
            if (errno == EAGAIN || errno == EWOULDBLOCK) {
                fseek(fi->file, -len, SEEK_CUR); 
            } else {
                printf("send file %s failed : %s\n", fi->name, strerror(errno));
                goto done;
            }
        } else if (send_len < len) {
            fseek(fi->file, send_len - len, SEEK_CUR);
        }
    }
    return;
done:
    network_del(net, fd);
    if (fi->file) {
        fclose(fi->file);
    }
    close(fd);
    RELEASE_OBJECT(FileInfo, fi);
    network_exit(net);
}

void on_write_name(network * net, int fd, void * ctx)
{
    FileInfo * fi = (FileInfo*)ctx;
    int ret = send(fd, fi->name+fi->name_translated, 256-fi->name_translated, 0);
    if (ret < 0) {
        if (errno == EAGAIN || errno == EWOULDBLOCK) {
            return;
        }

        printf("send name failed : %s\n", strerror(errno));
        goto failed;
    }

    fi->name_translated += ret;
    if (fi->name_translated == 256) {
        fi->file = fopen(fi->name, "rb");
        if (fi->file == 0) {
            printf("open file %s failed: %s\n", fi->name, strerror(errno));
            goto failed;
        }
        network_set(net, fd, 0, on_write_file, fi);
    }
    return;
failed:
    network_del(net, fd);
    if (fi->file) {
        fclose(fi->file);
    }
    close(fd);
    RELEASE_OBJECT(FileInfo, fi);
    network_exit(net);
}

void on_connected(network * net, int fd, int error, void * ctx)
{
    FileInfo * fi = (FileInfo*)ctx;
    if (error != 0) {
        RELEASE_OBJECT(FileInfo, fi);
        printf("connect to server failed\n");
        network_exit(net);
        return;
    }

    network_set(net, fd, 0, on_write_name, fi);
}

void sendfile(network * net, const char * file)
{
    printf("start to send %s\n", file);
    FileInfo * fi = GET_OBJECT(FileInfo);
    if (fi == 0) {
        printf("GET_OBJECT failed\n");
        return;
    }

    memset(fi->name, 0, 256);
    strcpy(fi->name, file);

    fi->name_translated = 0;

    if (network_connect(net, remote_ip, remote_port, on_connected, fi) == -1) {
        printf("connect to %s:%d failed\n", remote_ip, remote_port);
        RELEASE_OBJECT(FileInfo, fi);
        return;
    }
    network_loop(net);
}

int main(int argc, char * argv[])
{
    switch (argc) {
        case 2:
            is_listen = 1;
            local_port = atoi(argv[1]);
            break;
        case 3:
            is_listen = 1;
            local_ip = argv[1];
            local_port = atoi(argv[2]);
            break;
        case 1:
            Usage(argv[0]);
        default:
            is_listen = 0;
            remote_ip = argv[1];
            remote_port = atoi(argv[2]);
            break;
    }

    network * net = network_create(NETWORK_LT);

    if (is_listen) {
        network_listen(net, local_ip, local_port, on_accept, 0);
        network_loop(net);
    } else {
        for(int i = 3; i  < argc; i++) {
            sendfile(net, argv[i]);
        }
    }
    return 0;
}
